package drds.binlog.binlog_event.event;

import drds.binlog.binlog_event.CharSet;
import drds.binlog.binlog_event.LogByteBlock;
import drds.binlog.binlog_event.LogEvent;
import drds.binlog.common.Authors;
import drds.common.Author;

import java.io.IOException;
import java.io.Serializable;

/**
 * User_var_log_event. Every time a query uses the value of a user variable, a
 * User_var_log_event is written before the Query_log_event, to set the user
 * variable.
 */
@Author(name = Authors.LI_YANG)
public final class UserVarLogEvent extends LogEvent
{

    /**
     * the following is for user defined functions
     *
     * @see mysql-5.1.60//include/mysql_com.h
     */
    public static final int string_result = 0;
    public static final int real_result = 1;
    public static final int int_result = 2;
    public static final int row_result = 3;
    public static final int decimal_result = 4;
    /* user_var event data */
    public static final int uv_val_len_size = 4;
    public static final int uv_val_is_null = 1;
    public static final int uv_val_type_size = 1;
    public static final int uv_name_len_size = 4;
    public static final int uv_charset_number_size = 4;
    /**
     * Fixed data part: Empty
     * <p>
     * Variable data part:
     * <ul>
     * <li>4 bytes. the size of the user variable name.</li>
     * <li>The user variable name.</li>
     * <li>1 byte. Non-zero if the variable value is the SQL NULL value, 0
     * otherwise. If this byte is 0, the following parts exist in the event.</li>
     * <li>1 byte. The user variable logType. The value corresponds to elements of
     * enum Item_result defined in include/mysql_com.h.</li>
     * <li>4 bytes. The number of the character set for the user variable
     * (needed for a string variable). The character set number is really a
     * collation number that indicates a character set/collation pair.</li>
     * <li>4 bytes. The size of the user variable value (corresponds to member
     * val_len of class Item_string).</li>
     * <li>Variable-sized. For a string variable, this is the string. For a
     * float or integer variable, this is its value in 8 bytes.</li>
     * </ul>
     * Source : http://forge.mysql.com/wiki/MySQL_Internals_Binary_Log
     */
    private final String name;
    private final Serializable value;
    private final int type;
    private final int charsetNumber;
    private final boolean isNull;

    public UserVarLogEvent(LogHeader logHeader, LogByteBlock logByteBlock, FormatDescriptionLogEvent formatDescriptionLogEvent)
            throws IOException
    {
        super(logHeader);

        /* The Post-Header is empty. The Variable Data part begins immediately. */
        logByteBlock.setIndexAsOriginIndexAndOffset(formatDescriptionLogEvent.commonHeaderLength + formatDescriptionLogEvent.postHeaderLengths[user_var_event - 1]);
        //
        final int nameLength = (int) logByteBlock.getLittleEndian4UnsignedLongByIndex();
        name = logByteBlock.getFixString(nameLength); // UV_NAME_LEN_SIZE
        isNull = (0 != logByteBlock.get1SignedIntByIndex());

        if (isNull)
        {
            type = string_result;
            charsetNumber = 63; /* binary */
            value = null;
        } else
        {
            type = logByteBlock.get1SignedIntByIndex(); // UV_VAL_IS_NULL
            charsetNumber = (int) logByteBlock.getLittleEndian4UnsignedLongByIndex(); // buf + UV_VAL_TYPE_SIZE
            final int valueLength = (int) logByteBlock.getLittleEndian4UnsignedLongByIndex(); // buf +
            // UV_CHARSET_NUMBER_SIZE
            final int limit = logByteBlock.limit(); /* for restore */
            logByteBlock.limit(logByteBlock.index$originIndex() + valueLength);

            /* @see User_var_log_event::print */
            switch (type)
            {
                case real_result:
                    value = Double.valueOf(logByteBlock.getNextLittleEndian8Double()); // float8get
                    break;
                case int_result:
                    if (valueLength == 8)
                        value = Long.valueOf(logByteBlock.getLittleEndian8SignedLongByIndex()); // !uint8korr
                    else if (valueLength == 4)
                        value = Long.valueOf(logByteBlock.getLittleEndian4UnsignedLongByIndex());
                    else throw new IOException("Error INT_RESULT length: " + valueLength);
                    break;
                case decimal_result:
                    final int precision = logByteBlock.get1SignedIntByIndex();
                    final int scale = logByteBlock.get1SignedIntByIndex();
                    value = logByteBlock.getDecimal(precision, scale); // bin2decimal
                    break;
                case string_result:
                    String charsetName = CharSet.getJavaCharset(charsetNumber);
                    value = logByteBlock.getFixStringByIndex(valueLength, charsetName);
                    break;
                case row_result:
                    // this seems to be banned in MySQL altogether
                    throw new IOException("ROW_RESULT is unsupported");
                default:
                    value = null;
                    break;
            }
            logByteBlock.limit(limit);
        }
    }

    public final String getQuery()
    {
        if (value == null)
        {
            return "SET @" + name + " := NULL";
        } else if (type == string_result)
        {
            // TODO: do escaping !?
            return "SET @" + name + " := \'" + value + '\'';
        } else
        {
            return "SET @" + name + " := " + String.valueOf(value);
        }
    }
}
